---
title: Experimental WebSockets
---

<ExperimentalFeature>

**The experimental WebSockets implementation are currently [experimental](https://www.apollographql.com/docs/resources/product-launch-stages/#experimental-features) in Apollo Kotlin.** If you have feedback on them, please let us know via [GitHub issues](https://github.com/apollographql/apollo-kotlin/issues/new?assignees=&labels=Type%3A+Bug&template=bug_report.md) or in the [Kotlin Slack community](https://slack.kotl.in/).

</ExperimentalFeature>

Historically, WebSockets have been one of the most complex and error-prone parts of Apollo Kotlin because:

1. The WebSocket transport protocol has no official specification and different implementations have different behaviours.
2. WebSockets are stateful and making them work using the old Kotlin native memory model was challenging.
3. Because WebSockets are long-lived connections, they are more exposed to errors and knowing when (or if) to retry is hard.
4. Not all subscriptions happen on WebSockets. Some use [HTTP multipart](https://www.apollographql.com/docs/router/executing-operations/subscription-multipart-protocol/) for an example.

Starting with 4.0.0, Apollo Kotlin provides a new `com.apollographql.apollo.network.websocket` package containing new `WebSocketNetworkTransport` and `WebSocketEngine` implementations (instead of `com.apollographql.apollo.network.ws` for the current implementations).

The `com.apollographql.apollo.network.websocket` implementation provides the following:

1. Defaults to the [graphql-ws](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md) protocol, which has become the de facto standard. Using the other protocols is still possible but having a main, specified, protocol ensures we can write a good and solid test suite.
2. Does not inherit from the old memory model design, making the code considerably simpler. In particular, `WebSocketEngine` is now event based and no attempt at flow control is done. If your buffers grow too much, your subscription fails.
3. Plays nicely with the ApolloClient `retryOnError` API.
4. Handles different Subscription transports more consistently.

## Status

`.websocket` APIs are more robust than the non-experimental `.ws` ones, especially in scenarios involving retries/connection errors. They are safe to use in non-lib use cases.

The `@ApolloExperimental` annotation accounts for required API breaking changes based on community feedback. Ideally no change will be needed.

After a feedback phase, the current `.ws` APIs will become deprecated and the `.websocket` one promoted to stable by removing the `@ApolloExperimental` annotations. 

## Handling errors 


### Changing the WebSocket HTTP header on Error

The HTTP headers of `ApolloCall` are honored and different WebSockets are created for different header values. This means you can use an interceptor to change the header value on error:

```kotlin
private class UpdateAuthorizationHeaderInterceptor : ApolloInterceptor {
  @OptIn(ExperimentalCoroutinesApi::class)
  override fun <D : Operation.Data> intercept(request: ApolloRequest<D>, chain: ApolloInterceptorChain): Flow<ApolloResponse<D>> {
    return flow {
      // Retrieve a new access token every time
      val request = request.newBuilder()
          .addHttpHeader("Authorization", "Bearer ${accessToken()}")
          .build()

      emitAll(chain.proceed(request))
    }
  }
}
```

### Changing the connectionPayload on Error

The connection payload is `WsProtocol` specific and you can pass a lambda to your `WsProtocol` constructor that is evaluated every time a WebSocket needs to be created.

```kotlin
ApolloClient.Builder()
  .httpServerUrl(mockServer.url())
  .subscriptionNetworkTransport(
      WebSocketNetworkTransport.Builder()
          .serverUrl(mockServer.url())
          .wsProtocol(GraphQLWsProtocol(
              connectionPayload = {
                getFreshConnectionPayload()
              }
          ))
          .build()
  )
  .build()
```

### retryOnErrorInterceptor

By default, `ApolloClient` does not retry. You can override that behaviour with `retryOnErrorInterceptor`. You can combine that interceptor with the [`UpdateAuthorizationHeaderInterceptor`](#changing-the-websocket-http-header-on-error):

```kotlin
private object RetryException : Exception()

private class RetryOnErrorInterceptor : ApolloInterceptor {
    override fun <D : Operation.Data> intercept(request: ApolloRequest<D>, chain: ApolloInterceptorChain): Flow<ApolloResponse<D>> {
      var attempt = 0
      return flow {
        val request =  request.newBuilder()
                .addHttpHeader("Authorization", "Bearer ${accessToken()}")
                .build()
    
        emitAll(chain.proceed(request))
      }.onEach {
        when (val exception = it.exception) {
          is ApolloWebSocketClosedException -> {
            if (exception.code == 1002 && exception.reason == "unauthorized") {
              attempt = 0 // retry immediately
              throw RetryException
            }
          }
          is ApolloNetworkException -> {
            // Retry all network exceptions
            throw RetryException
          }
          else -> {
            // Terminate the subscription
          }
        }
      }.retryWhen { cause, _ ->
        cause is RetryException
      }
    }
}
```

## Migration guide

### Package name

In simple cases where you did not configure the underlying `WsProtocol` or retry logic, the migration should be about replacing `com.apollographql.apollo.network.ws` with `com.apollographql.apollo.network.websocket` everywhere:

```kotlin
// Replace
import com.apollographql.apollo.network.ws.WebSocketNetworkTransport
import com.apollographql.apollo.network.ws.WebSocketEngine
// etc...

// With
import com.apollographql.apollo.network.websocket.WebSocketNetworkTransport
import com.apollographql.apollo.network.websocket.WebSocketEngine
// etc...
```

Because we can't remove the current APIs just yet, the `ApolloClient.Builder` shortcut APIs are still pointing to the `.ws` implementations. To use the newer `.websocket` implementation, pass a `websocket.WebSocketNetworkTransport` directly:

```kotlin
// Replace
val apolloClient = ApolloClient.Builder()
    .serverUrl(serverUrl)
    .webSocketServerUrl(webSocketServerUrl)
    .webSocketEngine(myWebSocketEngine)
    .webSocketIdleTimeoutMillis(10_000)
    .build()

// With
import com.apollographql.apollo.network.websocket.*
    
// [...]

ApolloClient.Builder()
    .serverUrl(serverUrl)
    .subscriptionNetworkTransport(
        WebSocketNetworkTransport.Builder()
            .serverUrl(webSocketServerUrl)
            // If you didn't set a WsProtocol before, make sure to include this 
            .wsProtocol(SubscriptionWsProtocol())
            // If you were already using GraphQLWsProtocol, this is now the default
            //.wsProtocol(GraphQLWsProtocol())
            .webSocketEngine(myWebSocketEngine)
            .idleTimeoutMillis(10_000)
            .build()
    )
    .build()
```

### Connection init payload

If you were using `connectionPayload` before, you can now pass it as an argument directly. There is no `WsProtocol.Factory` anymore:

```kotlin
// Replace
GraphQLWsProtocol.Factory(
    connectionPayload = {
        mapOf("Authorization" to token)
    },
)

// With
GraphQLWsProtocol(
    connectionPayload = {
      mapOf("Authorization" to token)
    },
)
```

### Retrying on network errors

Apollo Kotlin 4 also comes with a default `retryOnErrorInterceptor` that uses a network monitor or exponential backoff to retry the subscription. 

If you want your subscription to be restarted automatically when a network error happens, use `retryOnError {}`:

```kotlin
// Replace
val apolloClient = ApolloClient.Builder()
    .serverUrl(serverUrl)
    .subscriptionNetworkTransport(
        WebSocketNetworkTransport.Builder()
            .serverUrl(webSocketServerUrl)
            .reopenWhen { _, attempt ->
              // exponential backoff
              delay(2.0.pow(attempt).seconds) // highlight-line
              true
            }
            .build()
    )
    .build()

// With 
val apolloClient = ApolloClient.Builder()
    .serverUrl(serverUrl)
    .subscriptionNetworkTransport(/*..*/)
    .retryOnError {
      /*
       * This is called for every GraphQL operation.
       * Only retry subscriptions.
       */
      it.operation is Subscription // highlight-line
    }
    .build()
```

You can also customize the retry logic using `retryOnErrorInterceptor`. Read more about it in the [network connectivity page](network-connectivity). 
